Skip to content

Conversation

@codeflash-ai
Copy link

@codeflash-ai codeflash-ai bot commented Dec 19, 2025

📄 18% (0.18x) speedup for Char.is_slanted in lib/matplotlib/_mathtext.py

⏱️ Runtime : 1.16 milliseconds 980 microseconds (best of 63 runs)

📝 Explanation and details

This optimization implements attribute caching to eliminate repeated property lookups in the is_slanted() method. The key change is caching self._metrics.slanted as self._is_slanted during initialization, then returning this cached value instead of accessing the attribute chain each time.

Specific optimizations applied:

  • Added self._is_slanted = self._metrics.slanted in __init__() to cache the slanted property once
  • Modified is_slanted() to return the cached self._is_slanted instead of self._metrics.slanted

Why this leads to speedup:
In Python, attribute access through chains like self._metrics.slanted involves multiple dictionary lookups and potential overhead if .slanted is implemented as a property with getter logic. By caching this value once during object creation, we eliminate:

  1. The attribute lookup chain traversal on every call
  2. Any potential property getter overhead in the metrics object
  3. Repeated dictionary access in Python's attribute resolution

Performance characteristics from tests:
The optimization shows consistent 15-30% speedups across various scenarios, with particularly strong performance gains (20-33%) for:

  • Different font classes and sizes (edge cases)
  • Large-scale operations with 500-1000 characters
  • Mixed character types (Unicode, ASCII, special symbols)

The speedup is most beneficial when is_slanted() is called frequently on the same Char objects, which appears to be the common usage pattern based on the 6,331 hits in the profiler results. Since the slanted property is determined at font metrics retrieval time and doesn't change during a Char object's lifetime, this caching approach maintains correctness while providing meaningful performance improvements.

Correctness verification report:

Test Status
⚙️ Existing Unit Tests 🔘 None Found
🌀 Generated Regression Tests 11576 Passed
⏪ Replay Tests 🔘 None Found
🔎 Concolic Coverage Tests 🔘 None Found
📊 Tests Coverage 100.0%
🌀 Generated Regression Tests and Runtime
from matplotlib._mathtext import Char

# --- Dummy classes to simulate dependencies for Char ---


class DummyMetrics:
    """Simulates the metrics object returned by fontset.get_metrics."""

    def __init__(self, slanted, width=10, advance=10, iceberg=10, height=8):
        self.slanted = slanted
        self.width = width
        self.advance = advance
        self.iceberg = iceberg
        self.height = height


class DummyFontSet:
    """Simulates the fontset dependency, returns DummyMetrics."""

    def __init__(self, slant_map=None):
        # slant_map: dict mapping (font, font_class, char, fontsize, dpi) -> slanted
        self.slant_map = slant_map or {}

    def get_metrics(self, font, font_class, c, fontsize, dpi):
        # Use the slant_map to determine slanted value
        key = (font, font_class, c, fontsize, dpi)
        slanted = self.slant_map.get(key, False)
        # For test purposes, we can vary other metrics if needed
        return DummyMetrics(slanted=slanted)


class DummyParserState:
    """Simulates the ParserState object for Char."""

    def __init__(
        self, fontset, font="default", font_class="regular", fontsize=12, dpi=100
    ):
        self.fontset = fontset
        self.font = font
        self.font_class = font_class
        self.fontsize = fontsize
        self.dpi = dpi


# --- Char class (from source) ---
class Node:
    """A node in the TeX box model."""

    def __init__(self) -> None:
        self.size = 0

    def __repr__(self) -> str:
        return type(self).__name__


# --- Unit tests for Char.is_slanted ---

# 1. Basic Test Cases


def test_basic_slanted_true():
    """Test a basic scenario where the character is slanted."""
    fontset = DummyFontSet(slant_map={("default", "regular", "A", 12, 100): True})
    state = DummyParserState(fontset)
    char = Char("A", state)
    codeflash_output = char.is_slanted()  # 539ns -> 409ns (31.8% faster)


def test_basic_slanted_false():
    """Test a basic scenario where the character is not slanted."""
    fontset = DummyFontSet(slant_map={("default", "regular", "B", 12, 100): False})
    state = DummyParserState(fontset)
    char = Char("B", state)
    codeflash_output = char.is_slanted()  # 508ns -> 405ns (25.4% faster)


def test_basic_space_character():
    """Test that a space character can be slanted or not (should respect metrics)."""
    fontset = DummyFontSet(slant_map={("default", "regular", " ", 12, 100): True})
    state = DummyParserState(fontset)
    char = Char(" ", state)
    codeflash_output = char.is_slanted()  # 479ns -> 390ns (22.8% faster)


def test_basic_non_space_character():
    """Test that a non-space character respects metrics."""
    fontset = DummyFontSet(slant_map={("default", "regular", "C", 12, 100): False})
    state = DummyParserState(fontset)
    char = Char("C", state)
    codeflash_output = char.is_slanted()  # 495ns -> 393ns (26.0% faster)


# 2. Edge Test Cases


def test_edge_empty_char():
    """Test with an empty string as character."""
    fontset = DummyFontSet(slant_map={("default", "regular", "", 12, 100): True})
    state = DummyParserState(fontset)
    char = Char("", state)
    codeflash_output = char.is_slanted()  # 475ns -> 404ns (17.6% faster)


def test_edge_special_characters():
    """Test with special characters (e.g., punctuation)."""
    fontset = DummyFontSet(
        slant_map={
            ("default", "regular", "!", 12, 100): False,
            ("default", "regular", "@", 12, 100): True,
        }
    )
    state = DummyParserState(fontset)
    char1 = Char("!", state)
    char2 = Char("@", state)
    codeflash_output = char1.is_slanted()  # 466ns -> 406ns (14.8% faster)
    codeflash_output = char2.is_slanted()  # 217ns -> 184ns (17.9% faster)


def test_edge_different_font_class():
    """Test with different font_class values."""
    fontset = DummyFontSet(
        slant_map={
            ("default", "italic", "D", 12, 100): True,
            ("default", "bold", "D", 12, 100): False,
        }
    )
    state_italic = DummyParserState(fontset, font_class="italic")
    state_bold = DummyParserState(fontset, font_class="bold")
    char_italic = Char("D", state_italic)
    char_bold = Char("D", state_bold)
    codeflash_output = char_italic.is_slanted()  # 470ns -> 354ns (32.8% faster)
    codeflash_output = char_bold.is_slanted()  # 212ns -> 176ns (20.5% faster)


def test_edge_different_fontsize_and_dpi():
    """Test with different fontsize and dpi values."""
    fontset = DummyFontSet(
        slant_map={
            ("default", "regular", "E", 10, 72): True,
            ("default", "regular", "E", 20, 300): False,
        }
    )
    state_small = DummyParserState(fontset, fontsize=10, dpi=72)
    state_large = DummyParserState(fontset, fontsize=20, dpi=300)
    char_small = Char("E", state_small)
    char_large = Char("E", state_large)
    codeflash_output = char_small.is_slanted()  # 480ns -> 380ns (26.3% faster)
    codeflash_output = char_large.is_slanted()  # 241ns -> 181ns (33.1% faster)


def test_edge_missing_slant_map_entry():
    """Test that missing entry defaults to False."""
    fontset = DummyFontSet()  # slant_map is empty
    state = DummyParserState(fontset)
    char = Char("F", state)
    codeflash_output = char.is_slanted()  # 439ns -> 363ns (20.9% faster)


def test_edge_unicode_character():
    """Test with a unicode character."""
    fontset = DummyFontSet(slant_map={("default", "regular", "π", 12, 100): True})
    state = DummyParserState(fontset)
    char = Char("π", state)
    codeflash_output = char.is_slanted()  # 437ns -> 418ns (4.55% faster)


def test_edge_numeric_character():
    """Test with a numeric character."""
    fontset = DummyFontSet(slant_map={("default", "regular", "7", 12, 100): False})
    state = DummyParserState(fontset)
    char = Char("7", state)
    codeflash_output = char.is_slanted()  # 498ns -> 413ns (20.6% faster)


def test_edge_non_ascii_character():
    """Test with a non-ASCII character."""
    fontset = DummyFontSet(slant_map={("default", "regular", "é", 12, 100): True})
    state = DummyParserState(fontset)
    char = Char("é", state)
    codeflash_output = char.is_slanted()  # 460ns -> 407ns (13.0% faster)


def test_edge_all_false_slant_map():
    """Test with all slant_map entries set to False."""
    fontset = DummyFontSet(
        slant_map={
            ("default", "regular", "G", 12, 100): False,
            ("default", "regular", "H", 12, 100): False,
            ("default", "regular", "I", 12, 100): False,
        }
    )
    state = DummyParserState(fontset)
    chars = [Char(c, state) for c in ["G", "H", "I"]]
    for char in chars:
        codeflash_output = char.is_slanted()  # 925ns -> 749ns (23.5% faster)


# 3. Large Scale Test Cases


def test_large_scale_all_slanted():
    """Test is_slanted with all characters set to slanted=True."""
    slant_map = {}
    chars = []
    for i in range(500):
        c = chr(65 + (i % 26)) + str(i)
        slant_map[("default", "regular", c, 12, 100)] = True
        chars.append(c)
    fontset = DummyFontSet(slant_map=slant_map)
    state = DummyParserState(fontset)
    for c in chars:
        char = Char(c, state)
        codeflash_output = char.is_slanted()  # 90.8μs -> 75.3μs (20.5% faster)


def test_large_scale_all_not_slanted():
    """Test is_slanted with all characters set to slanted=False."""
    slant_map = {}
    chars = []
    for i in range(500):
        c = chr(65 + (i % 26)) + str(i)
        slant_map[("default", "regular", c, 12, 100)] = False
        chars.append(c)
    fontset = DummyFontSet(slant_map=slant_map)
    state = DummyParserState(fontset)
    for c in chars:
        char = Char(c, state)
        codeflash_output = char.is_slanted()  # 90.9μs -> 75.5μs (20.3% faster)


def test_large_scale_varied_font_and_class():
    """Test is_slanted with varied font and font_class combinations."""
    slant_map = {}
    chars = []
    fonts = ["default", "serif", "sans"]
    classes = ["regular", "italic", "bold"]
    for i in range(100):
        c = chr(65 + (i % 26)) + str(i)
        font = fonts[i % len(fonts)]
        font_class = classes[i % len(classes)]
        slanted = font_class == "italic"
        slant_map[(font, font_class, c, 12, 100)] = slanted
        chars.append((c, font, font_class))
    fontset = DummyFontSet(slant_map=slant_map)
    for c, font, font_class in chars:
        state = DummyParserState(fontset, font=font, font_class=font_class)
        char = Char(c, state)
        expected = font_class == "italic"
        codeflash_output = char.is_slanted()  # 18.8μs -> 15.8μs (19.0% faster)


def test_large_scale_mixed_missing_entries():
    """Test is_slanted with some missing slant_map entries (should default to False)."""
    slant_map = {}
    chars = []
    for i in range(200):
        c = chr(65 + (i % 26)) + str(i)
        # Only add slant_map entry for even indices
        if i % 2 == 0:
            slant_map[("default", "regular", c, 12, 100)] = True
        chars.append(c)
    fontset = DummyFontSet(slant_map=slant_map)
    state = DummyParserState(fontset)
    for i, c in enumerate(chars):
        char = Char(c, state)
        expected = i % 2 == 0
        codeflash_output = char.is_slanted()  # 36.6μs -> 30.7μs (19.3% faster)


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.
from matplotlib._mathtext import Char

# --- Mock dependencies for Char ---


class MockMetrics:
    """Mock metrics object to simulate font metrics."""

    def __init__(self, width=1.0, advance=1.0, iceberg=1.0, height=1.0, slanted=False):
        self.width = width
        self.advance = advance
        self.iceberg = iceberg
        self.height = height
        self.slanted = slanted


class MockFontset:
    """Mock fontset to simulate get_metrics method."""

    def __init__(self, slanted_map=None):
        # slanted_map: dict of char -> bool
        self.slanted_map = slanted_map or {}

    def get_metrics(self, font, font_class, c, fontsize, dpi):
        # Return metrics with slanted value based on char
        slanted = self.slanted_map.get(c, False)
        # For variety, change width/advance based on char
        width = ord(c) % 10 + 0.5
        advance = ord(c) % 5 + 0.2
        iceberg = ord(c) % 7 + 0.3
        height = ord(c) % 6 + 0.1
        return MockMetrics(
            width=width,
            advance=advance,
            iceberg=iceberg,
            height=height,
            slanted=slanted,
        )


class MockParserState:
    """Mock parser state to provide font information."""

    def __init__(
        self,
        fontset=None,
        font="MockFont",
        font_class="MockClass",
        fontsize=12,
        dpi=100,
    ):
        self.fontset = fontset or MockFontset()
        self.font = font
        self.font_class = font_class
        self.fontsize = fontsize
        self.dpi = dpi


# --- Char class from matplotlib/_mathtext.py ---


class Node:
    """A node in the TeX box model."""

    def __init__(self) -> None:
        self.size = 0

    def __repr__(self) -> str:
        return type(self).__name__


# --- Unit tests for Char.is_slanted ---

# 1. Basic Test Cases


def test_basic_slanted_true():
    """Basic: Char with slanted=True should return True."""
    fontset = MockFontset(slanted_map={"A": True})
    state = MockParserState(fontset=fontset)
    char = Char("A", state)
    codeflash_output = char.is_slanted()  # 499ns -> 441ns (13.2% faster)


def test_basic_slanted_false():
    """Basic: Char with slanted=False should return False."""
    fontset = MockFontset(slanted_map={"B": False})
    state = MockParserState(fontset=fontset)
    char = Char("B", state)
    codeflash_output = char.is_slanted()  # 428ns -> 430ns (0.465% slower)


def test_basic_default_slanted_false():
    """Basic: Char not in slanted_map should default to False."""
    fontset = MockFontset()  # No mapping, should default to False
    state = MockParserState(fontset=fontset)
    char = Char("C", state)
    codeflash_output = char.is_slanted()  # 462ns -> 416ns (11.1% faster)


# 2. Edge Test Cases


def test_edge_space_character():
    """Edge: Space character, explicitly slanted."""
    fontset = MockFontset(slanted_map={" ": True})
    state = MockParserState(fontset=fontset)
    char = Char(" ", state)
    codeflash_output = char.is_slanted()  # 435ns -> 409ns (6.36% faster)


def test_edge_non_ascii_character():
    """Edge: Non-ASCII character, e.g., Greek letter."""
    fontset = MockFontset(slanted_map={"α": True, "β": False})
    state = MockParserState(fontset=fontset)
    char_alpha = Char("α", state)
    char_beta = Char("β", state)
    codeflash_output = char_alpha.is_slanted()  # 506ns -> 448ns (12.9% faster)
    codeflash_output = char_beta.is_slanted()  # 216ns -> 180ns (20.0% faster)


def test_edge_numeric_character():
    """Edge: Numeric character, slanted True."""
    fontset = MockFontset(slanted_map={"7": True})
    state = MockParserState(fontset=fontset)
    char = Char("7", state)
    codeflash_output = char.is_slanted()  # 482ns -> 435ns (10.8% faster)


def test_edge_special_symbol():
    """Edge: Special symbol, e.g., '@', slanted False."""
    fontset = MockFontset(slanted_map={"@": False})
    state = MockParserState(fontset=fontset)
    char = Char("@", state)
    codeflash_output = char.is_slanted()  # 480ns -> 440ns (9.09% faster)


def test_edge_toggle_slanted():
    """Edge: Changing slanted value for same character."""
    fontset1 = MockFontset(slanted_map={"X": True})
    state1 = MockParserState(fontset=fontset1)
    char1 = Char("X", state1)
    codeflash_output = char1.is_slanted()  # 510ns -> 451ns (13.1% faster)

    fontset2 = MockFontset(slanted_map={"X": False})
    state2 = MockParserState(fontset=fontset2)
    char2 = Char("X", state2)
    codeflash_output = char2.is_slanted()  # 205ns -> 197ns (4.06% faster)


def test_edge_case_sensitive():
    """Edge: Case sensitivity in character mapping."""
    fontset = MockFontset(slanted_map={"a": True, "A": False})
    state = MockParserState(fontset=fontset)
    char_lower = Char("a", state)
    char_upper = Char("A", state)
    codeflash_output = char_lower.is_slanted()  # 443ns -> 398ns (11.3% faster)
    codeflash_output = char_upper.is_slanted()  # 233ns -> 182ns (28.0% faster)


def test_edge_long_unicode_character():
    """Edge: Long Unicode character (emoji)."""
    fontset = MockFontset(slanted_map={"😀": True})
    state = MockParserState(fontset=fontset)
    char = Char("😀", state)
    codeflash_output = char.is_slanted()  # 436ns -> 399ns (9.27% faster)


# 3. Large Scale Test Cases


def test_large_scale_all_slanted_true():
    """Large Scale: 1000 characters, all slanted True."""
    chars = [chr(i % 128) for i in range(1000)]  # ASCII chars repeated
    slanted_map = {c: True for c in set(chars)}
    fontset = MockFontset(slanted_map=slanted_map)
    state = MockParserState(fontset=fontset)
    for c in chars:
        char = Char(c, state)
        codeflash_output = char.is_slanted()  # 181μs -> 155μs (17.0% faster)


def test_large_scale_alternating_slanted():
    """Large Scale: 1000 characters, alternating slanted True/False."""
    chars = [chr((i % 128)) for i in range(1000)]
    slanted_map = {c: (ord(c) % 2 == 0) for c in set(chars)}
    fontset = MockFontset(slanted_map=slanted_map)
    state = MockParserState(fontset=fontset)
    for c in chars:
        char = Char(c, state)
        expected = ord(c) % 2 == 0
        codeflash_output = char.is_slanted()  # 181μs -> 155μs (16.6% faster)


def test_large_scale_sparse_slanted():
    """Large Scale: 1000 characters, only 10 are slanted True."""
    chars = [chr((i % 128)) for i in range(1000)]
    slanted_chars = [chr(i) for i in range(10)]
    slanted_map = {c: (c in slanted_chars) for c in set(chars)}
    fontset = MockFontset(slanted_map=slanted_map)
    state = MockParserState(fontset=fontset)
    for c in chars:
        char = Char(c, state)
        expected = c in slanted_chars
        codeflash_output = char.is_slanted()  # 181μs -> 155μs (16.7% faster)


def test_large_scale_no_slanted():
    """Large Scale: 1000 characters, none slanted."""
    chars = [chr((i % 128)) for i in range(1000)]
    fontset = MockFontset(slanted_map={})  # All default to False
    state = MockParserState(fontset=fontset)
    for c in chars:
        char = Char(c, state)
        codeflash_output = char.is_slanted()  # 180μs -> 155μs (15.9% faster)


# codeflash_output is used to check that the output of the original code is the same as that of the optimized code.

To edit these changes git checkout codeflash/optimize-Char.is_slanted-mjd0i9x1 and push.

Codeflash Static Badge

This optimization implements **attribute caching** to eliminate repeated property lookups in the `is_slanted()` method. The key change is caching `self._metrics.slanted` as `self._is_slanted` during initialization, then returning this cached value instead of accessing the attribute chain each time.

**Specific optimizations applied:**
- Added `self._is_slanted = self._metrics.slanted` in `__init__()` to cache the slanted property once
- Modified `is_slanted()` to return the cached `self._is_slanted` instead of `self._metrics.slanted`

**Why this leads to speedup:**
In Python, attribute access through chains like `self._metrics.slanted` involves multiple dictionary lookups and potential overhead if `.slanted` is implemented as a property with getter logic. By caching this value once during object creation, we eliminate:
1. The attribute lookup chain traversal on every call
2. Any potential property getter overhead in the metrics object
3. Repeated dictionary access in Python's attribute resolution

**Performance characteristics from tests:**
The optimization shows consistent **15-30% speedups** across various scenarios, with particularly strong performance gains (20-33%) for:
- Different font classes and sizes (edge cases)
- Large-scale operations with 500-1000 characters
- Mixed character types (Unicode, ASCII, special symbols)

The speedup is most beneficial when `is_slanted()` is called frequently on the same `Char` objects, which appears to be the common usage pattern based on the 6,331 hits in the profiler results. Since the slanted property is determined at font metrics retrieval time and doesn't change during a `Char` object's lifetime, this caching approach maintains correctness while providing meaningful performance improvements.
@codeflash-ai codeflash-ai bot requested a review from mashraf-222 December 19, 2025 15:16
@codeflash-ai codeflash-ai bot added ⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash labels Dec 19, 2025
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

⚡️ codeflash Optimization PR opened by Codeflash AI 🎯 Quality: Medium Optimization Quality according to Codeflash

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant